/**
 * @file
 *           distributiedfilessystem.h
 * @brief
 *          <fillin>purpose
 *
 * @par Author (last changes):
 *          - Herea Adrian
 *          
 * @par Description:
 *
 *
 * @par Module-History:
 * @verbatim
 *  Date        Author				Reason
 *  15.01.2010  Herea Adrian         Initial creation
 *
 * @endverbatim
 *
 * @par Copyright Notice:
 * @verbatim
 * Copyright (C) 2010
 * All Rights Reserved.
 * The reproduction, transmission or use of this document or its contents is
 * not permitted without express written authority.
 * Offenders will be liable for damages. All rights, including rights created
 * by patent grant or registration of a utility model or design, are reserved.
 **/
using System;
using System.Collections.Generic;

using System.Text;
using Client.Protocol;
using System.Net;
using System.Net.Sockets;
using System.Threading;

namespace Client.Protocol
{
    class DistributiedFilesSystemMaster
    {
        string MSG_BEGIN_TAG = "$$$$";
        string MSG_END_TAG = "####";

        public Socket m_clientSocket;
        private Thread th_client_thread;
        public List<Socket> m_clientSockets = new List<Socket>();


        #region staffs for mutlithreding ....not in use yet..hope :D
      //  private Thread[] m_client_process_incoming_mesage;

        public void WaitForData()
        {
            th_client_thread = new Thread(new ThreadStart(client_thread));
            th_client_thread.Start();
        }

        private bool doCommand(string message)
        {
            bool rez = true;
            int beginTag = message.IndexOf(MSG_BEGIN_TAG);
            string command = message.Substring(beginTag + 4);
            int endTag = command.IndexOf(MSG_END_TAG);
            command = command.Substring(0, endTag - 4);
            Command cmd;
            cmd = Protocol.command(command);
            execCommand(cmd);

            return rez;
        }
        private void client_thread()
        {
            string command = "";
            bool loop = true;
            byte[] buffer = new byte[2048];
            int res = 0;
            while (loop)
            {
                do
                {
                    try
                    {
                        res = m_clientSocket.Receive(buffer);
                        if (res > 0)
                        {
                            command = command + Encoding.ASCII.GetString(buffer, 0, res);
                        }
                        if (command != "")
                        {
                            while (command.IndexOf(MSG_END_TAG) >= 0)
                            {
                                string _command = command.Substring(0, command.IndexOf(MSG_END_TAG) + MSG_END_TAG.Length);
                                doCommand(_command);
                                command = command.Substring(command.IndexOf(MSG_END_TAG) + MSG_END_TAG.Length);
                            }
                        }
                    }

                    catch (System.Net.Sockets.SocketException)
                    {
                        if (command != "")
                        {
                            while (command.IndexOf(MSG_END_TAG) >= 0)
                            {
                                string _command = command.Substring(0, command.IndexOf(MSG_END_TAG) + MSG_END_TAG.Length);
                                doCommand(_command);
                                command = command.Substring(command.IndexOf(MSG_END_TAG) + MSG_END_TAG.Length);
                            }
                        }

                    }

                    System.Threading.Thread.Sleep(25);
                } while (res > 1);
            }

        }

        #endregion

        private Command wait_one_response(Socket clientSocket)
        {
            string command = "";
            bool loop = true;
            byte[] buffer = new byte[2048];
            int res = 0;
            Command retCmd = null;
            while (loop)
            {
                res = clientSocket.Receive(buffer);
                if (res > 0)
                {
                    command = command + Encoding.ASCII.GetString(buffer, 0, res);
                }
                if (command != "")
                {
                    if (command.IndexOf(MSG_END_TAG) >= 0)
                    {
                        string _command = command.Substring(0, command.IndexOf(MSG_END_TAG) + MSG_END_TAG.Length);
                        int beginTag = _command.IndexOf(MSG_BEGIN_TAG);
                        _command = _command.Substring(beginTag + 4);
                        int endTag = _command.IndexOf(MSG_END_TAG);
                        _command = _command.Substring(0, endTag);
                        retCmd = Protocol.command(_command);
                        loop = false;
                    }
                }
                System.Threading.Thread.Sleep(25);
            }
            return retCmd;
        }



        #region how manage the commands from client
        //proces the command recived from servers/ or client for this  service
        private Command execCommand(Command cmd)
        {
            Command retCommand = new Command();
            if (cmd.ID == (int)Constants.cmd.CMD_GET_SERVERS_ADRESSES)
            {
                retCommand = getServerListFrom(cmd.Params[0].Val, cmd.Params[1].Val);
            }
            else

            switch (cmd.ID)
            {
                case (int)Constants.cmd.CMD_CHANGE_DIR:
                    {
                        if (cmd.Params.Count > 0)
                        {
                            retCommand = askChangeDirectoryTo(cmd.Params[0].Val);
                        }
                        else
                        {
                            retCommand.ID = (int)Constants.cmd.CMD_LIST_END;
                        }
                    }
                    break;
                case (int)Constants.cmd.CMD_MAKE_DIR:
                    {
                        if (cmd.Params.Count > 0)
                        {
                            retCommand = MakeDirector(cmd.Params[0].Val);
                        }
                        else
                        {
                            retCommand.ID = (int)Constants.cmd.CMD_LIST_END;
                        }

                    }
                    break;

                case (int)Constants.cmd.CMD_LIST_DIR:
                    {
                        if (cmd.Params.Count > 0)
                        {
                            retCommand = askChangeDirectoryTo(cmd.Params[0].Val);
                            if (retCommand.ID != (int)Constants.cmd.CMD_LIST_END)
                            {
                                retCommand = ListDirectorContent(cmd.Params[0].Val);
                            }
                        }
                        else
                        {
                            retCommand.ID = (int)Constants.cmd.CMD_LIST_END;
                        }

                    }
                    break;
                case (int)Constants.cmd.CMD_NEW_FILE:
                    {

                        if (cmd.Params.Count > 0)
                        {
                            //retCommand = askChangeDirectoryTo(cmd.Params[0].Val);
                            //if (retCommand.ID != (int)Constants.cmd.CMD_LIST_END)
                            //{
                                retCommand = CreateNewFile(cmd.Params[0].Val);
                            //}
                        }
                        else
                        {
                            retCommand.ID = (int)Constants.cmd.CMD_LIST_END;
                        }
                    }
                    break;
                case (int)Constants.cmd.CMD_CONECT_TO_SERVERS:
                    {

                        if (cmd.Params.Count > 0)
                        {
                          /*  retCommand = connectToServers(cmd.Params[0].Val);
                            if (retCommand.ID != (int)Constants.cmd.CMD_CONECT_TO_SERVERS)
                            {*/
                                retCommand = connectToServers(cmd);
                           /* }*/
                        }
                        else
                        {
                            retCommand.ID = (int)Constants.cmd.CMD_LIST_END;
                        }
                    }
                    break;
                default:
                    {
                        retCommand.ID = (int)Constants.cmd.CMD_LIST_END;
                    }
                    break;


            }
            return retCommand;
        }

        
        private Command getServerListFrom(string ipS, string portS)
        {
            Command retCommand = new Command();
            try
            {
                m_clientSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

                // Cet the remote IP address
                IPAddress ip = IPAddress.Parse(ipS);
                int iPortNo = System.Convert.ToInt16(portS);
                // Create the end point 
                IPEndPoint ipEnd = new IPEndPoint(ip, iPortNo);
                // Connect to the remote host
                m_clientSocket.Connect(ipEnd);
                if (m_clientSocket.Connected)
                {

                    Command newcmd = new Command();
                    newcmd.ID = (int)Constants.cmd.CMD_GET_SERVERS_ADRESSES;
                    sendResponse(Protocol.protocol(newcmd), m_clientSocket);
                    retCommand = wait_one_response(m_clientSocket);
                    m_clientSocket.Close();
                }
            }
            catch (Exception)
            {
                retCommand.ID = (int)Constants.cmd.CMD_LIST_END;
            }
            return retCommand;
        }

        private Command connectToServers(Command cmd)
        {
            Command retCommand = new Command();
            if (m_clientSockets.Count > 0)
            { 
                for (int i = 0; i < m_clientSockets.Count - 1; i+=2)
            {
                m_clientSockets[i].Disconnect(false);
            }
            }
            m_clientSockets = new List<Socket>();
            retCommand.ID = (int)Constants.cmd.CMD_CONECT_TO_SERVERS;
            Socket newsocket;
            for (int i = 0; i < cmd.Params.Count - 1; i+=2)
            {
                newsocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

                IPAddress ip = IPAddress.Parse(cmd.Params[i].Val);
                int iPortNo = System.Convert.ToInt16(cmd.Params[i+1].Val);
                // Create the end point 
                IPEndPoint ipEnd = new IPEndPoint(ip, iPortNo);
                // Connect to the remote host
                newsocket.Connect(ipEnd);
                if (newsocket.Connected)
                {
                    retCommand.addParam(cmd.Params[i]);
                    retCommand.addParam(cmd.Params[i + 1]);
                    m_clientSockets.Add(newsocket);
                }
            }
            return retCommand;
        
        }
        private Command askChangeDirectoryTo(string path)
        {
            Command retCommand = new Command();
            retCommand.ID = (int)Constants.cmd.CMD_CHANGE_DIR;
            Command newcmd = new Command();
            newcmd.ID = (int)Constants.cmd.CMD_CHANGE_DIR;
            
            newcmd.addParam(new Param("path",path));
            bool possible = false;
            for (int i = 0; i < m_clientSockets.Count; i++)
            {
                sendResponse(Protocol.protocol(newcmd), m_clientSockets[i]);
                Command partialCommand = wait_one_response(m_clientSockets[i]);
                possible |= bool.Parse(partialCommand.Params[0].Val);
            }
            retCommand.Params.Add(new Param("pathExist", possible.ToString()));
            retCommand.Params.Add(new Param("path", path));
            return retCommand;
        }

        private Command MakeDirector(string path)
        {
            //string actualPath = (m_currentDir.CompareTo("") != 0) ? (m_currentDir + "\\" + path) : path;

            //1. first check if path/dir exists
            //2. create dir on first server

            Command retCommand = new Command();

            Command newcmd = new Command();
            newcmd.ID = (int)Constants.cmd.CMD_MAKE_DIR;

            Command retCmd = new Command();
            if (!checkPathExists(path))
            {
                if(m_clientSockets.Count > 0)
                {
                    newcmd.clearParams();
                    newcmd.ID = (int)Constants.cmd.CMD_MAKE_DIR;
                    Param createDirParam = new Param("Param", path);
                    newcmd.addParam(createDirParam);

                    sendResponse(Protocol.protocol(newcmd), m_clientSockets[0]);
                    retCommand = wait_one_response(m_clientSockets[0]);
                }
            }
            else
            {
                retCmd.ID = (int)Constants.cmd.CMD_NEW_FILE;
                //file already exists
                retCmd.addParam(new Param(Constants.STATUS_PARAM, "Directory " + path + " already exists"));
            }

            return retCommand;
        }

        private Command ListDirectorContent(string path)
        {
            Command newcmd = new Command();
            newcmd.ID = (int)Constants.cmd.CMD_LIST_DIR;
            newcmd.addParam(new Param("path", path));

            Command retCmd = new Command();
            retCmd.ID = (int)Constants.cmd.CMD_LIST_DIR;
            for (int i = 0; i < m_clientSockets.Count; i++)
            {
                sendResponse(Protocol.protocol(newcmd), m_clientSockets[i]);
                Command partialCommand = wait_one_response(m_clientSockets[i]);

                //merge the lists
                for (int j = 0; j < partialCommand.Params.Count; j++)
                {
                    bool exist = false;
                    for (int t = 0; t < retCmd.Params.Count; t++)
                    {
                        if (retCmd.Params[t].Val == partialCommand.Params[j].Val)
                        {
                            exist = true;
                            break;
                        }
                    }
                    if (!exist)
                    {
                        retCmd.addParam(partialCommand.Params[j]);
                        j++;
                        retCmd.addParam(partialCommand.Params[j]);
                    }
                }

            }
            return retCmd;
        }

        private Command CreateNewFile(string path)
        {
            //string actualPath = (m_currentDir.CompareTo("") != 0) ? (m_currentDir + "\\" + path) : path;

            //1. first check if file exists
            //2. query for available space 
            //3. create file on first server that has more than 0 available space
            Command newcmd = new Command();
            newcmd.ID = (int)Constants.cmd.CMD_QUERY_AVAILABLE_SPACE;

            Command retCmd = new Command();
            if (!checkPathExists(path))
            {

                for (int i = 0; i < m_clientSockets.Count; i++)
                {
                    sendResponse(Protocol.protocol(newcmd), m_clientSockets[i]);
                    Command partialCommand = wait_one_response(m_clientSockets[i]);

                    //check available space
                    if (partialCommand.Params.Count > 0)
                    {
                        //should have only 1 param -> available space
                        try
                        {
                            int availableSpace = Int32.Parse(partialCommand.Params[0].Val.ToString());
                            if (availableSpace > 0)
                            {
                                //got first server with available space -> create file here
                                newcmd.clearParams();
                                newcmd.ID = (int)Constants.cmd.CMD_NEW_FILE;
                                Param createFileParam = new Param("Param", path);
                                newcmd.addParam(createFileParam);

                                sendResponse(Protocol.protocol(newcmd), m_clientSockets[i]);

                                //put the response in retCmd
                                retCmd = wait_one_response(m_clientSockets[i]);
                                break;
                            }
                        }
                        catch (System.Exception)
                        {
                            retCmd.addParam(new Param(Constants.STATUS_PARAM, "Error while creating new file"));
                            break;
                        }
                    }

                }

                //retCmd was not used -> means that the previous command returned 0 (disk full)
                if (retCmd.Params.Count == 0)
                {
                    retCmd.ID = (int)Constants.cmd.CMD_NEW_FILE;
                    //no space available on any server
                    retCmd.addParam(new Param(Constants.STATUS_PARAM, "Disk is full"));
                }
            }
            else
            {
                retCmd.ID = (int)Constants.cmd.CMD_NEW_FILE;
                //file already exists
                retCmd.addParam(new Param(Constants.STATUS_PARAM, "File " + path + " already exists"));
            }

            return retCmd;
        }

        //get commmand in command line format and convert it in Command object
        public Command ParseString2Cmd(String cmd)
        {
            int cmdType = (int)Constants.cmd.CMD_LIST_END;
            cmd = cmd.Trim();
            String[] tokenns = cmd.Split(new char[] { ' ' });
            Command newCmd = null;
            if (tokenns[0].CompareTo("") != 0)
            {
                cmdType = getCmdId(tokenns[0]);
            }
            newCmd = new Command();
            newCmd.ID = cmdType;
            if (tokenns.Length > 1 && cmdType != (int)Constants.cmd.CMD_LIST_END)
            {
                for (int i = 1; i < tokenns.Length; i++)
                {
                    newCmd.addParam(new Param("Param", tokenns[i]));
                }
            } //excceptions
            else
            {
                if (cmdType == (int)Constants.cmd.CMD_LIST_DIR)
                {
                    newCmd.addParam(new Param("Param", ""));
                }
            }
            return newCmd;
        }

        //get the commmand from command line and send it to servers  
        public Command executStringCommand(string cmd)
        {
            Command retCommand;//= new Command();
            Command requestCommand = ParseString2Cmd(cmd);
            retCommand = execCommand(requestCommand);

            return retCommand;

        }

        private int getCmdId(string token)
        {
            int cmdType = (int)Constants.cmd.CMD_LIST_END;
            if (token == "cd")
            {
                cmdType = (int)Constants.cmd.CMD_CHANGE_DIR;
            }

            else if (token == "ls")
            {
                cmdType = (int)Constants.cmd.CMD_LIST_DIR;
            }

            else if (token == "mkdir")
            {
                cmdType = (int)Constants.cmd.CMD_MAKE_DIR;
            }

            else if (token == "md")
            {
                cmdType = (int)Constants.cmd.CMD_CHANGE_DIR;
            }

            else if (token == "nf")
            {
                cmdType = (int)Constants.cmd.CMD_NEW_FILE;
            }

            else if (token == "sw")
            {
                cmdType = (int)Constants.cmd.CMD_SHOW_FILE;
            }

            else if (token == "getSLst")
            {
                cmdType = (int)Constants.cmd.CMD_GET_SERVERS_ADRESSES;
            }
            else if (token == "connectS")
            {
                cmdType = (int)Constants.cmd.CMD_CONECT_TO_SERVERS;
            }
            else if (token == "exit")
            {
                cmdType = (int)Constants.cmd.CMD_EXIT;
            }
            return cmdType;
        }
        #endregion

        //send ansyncron data to server
        protected void sendResponse(string data, Socket clientSocket)
        {
            byte[] byteData = Encoding.ASCII.GetBytes(MSG_BEGIN_TAG + data + MSG_END_TAG);
            try
            {
                // Begin sending the data to the remote device.
                if (byteData.Length > 0)
                {
                    clientSocket.BeginSend(byteData, 0, byteData.Length, 0,
                    new AsyncCallback(this.SendCallback), clientSocket);
                }

            }
            catch (System.Net.Sockets.SocketException se)
            {
                // MessageBox.Show(se.Message);
            }
        }

        protected void SendCallback(IAsyncResult ar)
        {
            // Retrieve the socket from the async state object.
            Socket handler = (Socket)ar.AsyncState;
            try
            {
                // Complete sending the data to the remote device.
                int bytesSent = handler.EndSend(ar);
            }
            catch (System.Net.Sockets.SocketException se)
            {
                //   MessageBox.Show(se.Message);
            }
        }

        private bool checkPathExists(string path)
        {
            bool retVal = false;
            Command newcmd = new Command();
            newcmd.ID = (int)Constants.cmd.CMD_PATH_EXISTS;

            Command retCmd = new Command();
            for (int i = 0; i < m_clientSockets.Count; i++)
            {
                Param pathExists = new Param("Param", path);
                newcmd.addParam(pathExists);
                sendResponse(Protocol.protocol(newcmd), m_clientSockets[i]);
                Command partialCommand = wait_one_response(m_clientSockets[i]);

                if (partialCommand.Params.Count > 0)
                {
                    if (Constants.STATUS_OK_VALUE.CompareTo(partialCommand.Params[0].Val) == 0)
                    {
                        //path exists
                        retVal = true;
                        break;
                    }
                }
            }
            return retVal;
        }
    }
}
